
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace pong1
{

    /// <summary>
    /// 'class UserInput' provides the input wrapper and GUI for the game.
    /// 1. Displays game menu and creates input streams based on the menu selection 
    /// 2. Gets input from the user 
    /// 3. Translates that input into language that game can understand and places it on event collection
    /// 
    /// </summary>
    public class UserInput : Microsoft.Xna.Framework.DrawableGameComponent
    {
        IInput input0;      // Contains the input object for getting the menu input (always keyboard )
        IInput input1;      // Contains the input object for the player (keyboard OR mouse )
        IInput input2;      // Used when one player is on the keyboard and the other on the mouse
        Resources.GAMEDESCRIPTION gameDescription;    // Remembers what type of game is being played
                    // this variable(the subject) is being subscribed to by PongPaddle(the observer)
        

        SpriteBatch spriteBatch;    // Stores reference to spritebatch retrieved from Services object
        SpriteFont spriteFont;      // Stores reference to spritefont retrieved from Services object
        String menu;                // Stores the menu for the game
        Game game;
        bool menuDisplayed;         // Variable that tells the game whether or not to display the main menu
                                    // Once the user selects an option from the menu, PongGame adds the components to the components collection
                                    // this variable(the subject) is being subscribed to by PongGame(the observer)
        EventCollection eventCollection;    // Available from Services, this is the list of pending events
        float fInputTimeAccumulator;    // Regulate the input to every Resources.MIN_INPUT_TIME seconds

        public event EventHandler<GameDescriptionSetEventArgs> gameDescriptionSet;
        protected virtual void OnGameDescriptionSet(GameDescriptionSetEventArgs e)
        {
            if (gameDescriptionSet != null)
            {
                gameDescriptionSet(this, e);
            }
        }
        public Resources.GAMEDESCRIPTION GameDescription
        {
            get { return gameDescription; }
            set
            {
                if (gameDescription == value) // no change in variable
                    return;
                else
                {
                    OnGameDescriptionSet(new GameDescriptionSetEventArgs(value));
                    gameDescription = value;
                }
            }
        }
        
        public event EventHandler<MenuDisplayedEventArgs> menuWasDisplayed;
        protected virtual void OnMenuWasDisplayed(MenuDisplayedEventArgs e)
        {
            if (menuWasDisplayed != null)
            {
                menuWasDisplayed(this, e);
            }
        }

        bool MenuDisplayed 
        {
            get { return menuDisplayed; }
            set 
            {
                if (menuDisplayed == value) // no change in variable
                    return;
                else
                {
                    OnMenuWasDisplayed(new MenuDisplayedEventArgs(value));
                    menuDisplayed = value;
                }
            }
        }

        public enum INPUTSOURCE
        {
            KEYBOARD, MOUSE
        }

        
        

        /// <summary>
        /// Initialises a menu string. Stores reference to the game object
        /// </summary>
        public UserInput(Game game)
            : base(game)
        {
            menu = Resources.MENU_TEXT;
            fInputTimeAccumulator = 0;
            this.game = game;
        }


        /// <summary>
        /// Writes text on screen at specified position
        /// </summary>
        private void displayText(String text, String textPosition)
        {
            spriteFont = (SpriteFont)Game.Services.GetService(typeof(SpriteFont));
            spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
            Vector2 v;
            int fontHeight = 30;
            switch(textPosition.ToLower())
            {
                case "bottom":
                    v = new Vector2(GraphicsDevice.Viewport.Width / 3, GraphicsDevice.Viewport.Height - spriteFont.LineSpacing);
                    break;
                case "top":
                    v = new Vector2(GraphicsDevice.Viewport.Width / 3, 0);
                    break;
                case "center":
                    v = new Vector2(GraphicsDevice.Viewport.Width / 3, GraphicsDevice.Viewport.Height / 3);
                    break;
                default:
                    v = new Vector2(GraphicsDevice.Viewport.Width / 3, GraphicsDevice.Viewport.Height - fontHeight);
                    break;

            }
            spriteBatch.Begin();
            spriteBatch.DrawString(spriteFont, text, v, Color.White);
            spriteBatch.End();
        }

        /// <summary>
        /// Creates the input streams for mouse or keyboard
        /// </summary>
        private IGUIFactory createInputStream(INPUTSOURCE inputOption)
        {
            IGUIFactory guiFactory;
            switch (inputOption)
                {
                    case INPUTSOURCE.KEYBOARD:
                        guiFactory = new KeyboardInputFactory();
                        break;
                    case INPUTSOURCE.MOUSE:
                        guiFactory = new MouseInputFactory();
                        break;
                    default:
                        guiFactory = new MouseInputFactory();
                        break;
                }
            return guiFactory;
        }

        /// <summary>
        /// This retrieves the input from the input layer, then maps it to generic commands, e.g. UP, DOWN for paddle 1, paddle 2 etc
        /// And places the appropriate user input events on the event collection
        /// </summary>
        private void getInput()
        {
            // Before doing anything else, check whether the user has pressed 'escape' during the game. This will take the user back to the menu screen
            String menuInputKey = (String)input0.getKeyboardInput();
            if (menuInputKey == "esc")
            {
                MenuDisplayed = false;  // This will call PongGame's MenuChanged(...) function
                gameDescription = Resources.GAMEDESCRIPTION.NONE;
                return;
            }
            eventCollection = (EventCollection)Game.Services.GetService(typeof(EventCollection));
            
            if( (GameDescription == Resources.GAMEDESCRIPTION.ONE_PLAYER_KEYBOARD) || (GameDescription == Resources.GAMEDESCRIPTION.TWO_PLAYERS_KEYBOARD))
            {
                String pressedKey = (String)input1.getInput();
                String [] inputs;
                pressedKey = pressedKey.Trim();
                inputs = pressedKey.Split(' ');

                for (int i = 0; (i < inputs.Length) && inputs[i] != ""; i++)
                { 
                    /// Put input[i] on event collection
                    /// if input[i] is arrow keys, then command for player 1
                    /// else if input[i] is WSAD keys AND two player game is enabled, then command for player 2
                    Event e = new Event();
                    if (inputs[i] == "UP" || inputs[i] == "DOWN" || inputs[i] == "RIGHT" || inputs[i] == "LEFT")
                    {
                        e.EventName = "uiarrows";
                    }
                    else if ((inputs[i] == "W" || inputs[i] == "S" || inputs[i] == "D" || inputs[i] == "A") && GameDescription == Resources.GAMEDESCRIPTION.TWO_PLAYERS_KEYBOARD)
                    {
                        e.EventName = "uiwsad";
                    }
                    else
                    { 
                        // Erroneous input. Make error log.
                    }

                    switch (inputs[i])
                    {
                        case "UP":
                        case "W":
                            e.EventArguments[0].ArgumentName  = "move";
                            e.EventArguments[0].ArgumentValue = "up";
                            e.EventArguments[0].ArgumentType = "string";
                            break;

                        case "DOWN":
                        case "S":
                            e.EventArguments[0].ArgumentName = "move";
                            e.EventArguments[0].ArgumentValue = "down";
                            e.EventArguments[0].ArgumentType = "string";
                            break;

                        case "RIGHT":
                        case "D":
                            e.EventArguments[0].ArgumentName  = "move";
                            e.EventArguments[0].ArgumentValue = "right";
                            e.EventArguments[0].ArgumentType = "string";
                            break;

                        case "LEFT":
                        case "A":
                            e.EventArguments[0].ArgumentName  = "move";
                            e.EventArguments[0].ArgumentValue = "left";
                            e.EventArguments[0].ArgumentType = "string";
                            break;
                        default:
                            break;
                    }

                    if (e.EventName != "")       // Add an event for player(s)
                        eventCollection.Add(e); 

                }
                
                if (GameDescription == Resources.GAMEDESCRIPTION.ONE_PLAYER_KEYBOARD)     // Add a simulation event for player2 if there is only one player
                {
                    Event e1 = new Event();
                    e1.EventName = "sim2";
                    eventCollection.Add(e1);
                }

            }
            else if(GameDescription == Resources.GAMEDESCRIPTION.ONE_PLAYER_MOUSE)
            {
                String smouseInformation = (String)input1.getInput();
                String[] inputs;
                smouseInformation = smouseInformation.Trim().ToLower();
                inputs = smouseInformation.Split(',');
                
                Event e = new Event();
                e.EventName = "uimouse";

                e.EventArguments[0].ArgumentName = "yvalue";
                e.EventArguments[0].ArgumentValue = inputs[0];
                e.EventArguments[0].ArgumentType = "int";

                if (inputs[1] == "left")      // flick paddle's left side
                {
                    e.EventArguments[0].ArgumentName = "mbpressed";
                    e.EventArguments[0].ArgumentValue = "left";
                    e.EventArguments[0].ArgumentType = "string";
                }
                else if (inputs[1] == "right")      // flick paddle's right side
                {
                    e.EventArguments[0].ArgumentName = "mbpressed";
                    e.EventArguments[0].ArgumentValue = "right";
                    e.EventArguments[0].ArgumentType = "string";
                }

                if (e.EventName != "")
                    eventCollection.Add(e);
                
                // Add a simulation event for player2 
                Event e1 = new Event();
                e1.EventName = "sim2";
                eventCollection.Add(e1);

            }
            else if (GameDescription == Resources.GAMEDESCRIPTION.SIMULATION)
            { 
                // Put msgs on event collection for paddle1 and paddle2 to generate their own move for this frame
                Event e1 = new Event();
                Event e2 = new Event();

                e1.EventName = "sim1";
                e2.EventName = "sim2";
                eventCollection.Add(e1);
                eventCollection.Add(e2);

                
            }
            else if (GameDescription == Resources.GAMEDESCRIPTION.TWO_PLAYERS_KEYBOARD_MOUSE)
            {
                String pressedKey = (String)input1.getInput();
                String[] inputs;
                pressedKey = pressedKey.Trim();
                inputs = pressedKey.Split(' ');

                for (int i = 0; (i < inputs.Length) && inputs[i] != ""; i++)
                {
                    /// Put input[i] on event collection
                    /// if input[i] is arrow keys, then command for player 1
                    /// else if input[i] is WSAD keys AND two player game is enabled, then command for player 2
                    /// else if input[i] is escape key, then the game has to end
                    /// else no valid command has been given
                    Event e1 = new Event();
                    if (inputs[i] == "UP" || inputs[i] == "DOWN" || inputs[i] == "RIGHT" || inputs[i] == "LEFT")
                    {
                        e1.EventName = "uiarrows";
                    }
                    else
                    {
                        // Erroneous input. Make error log.
                    }

                    switch (inputs[i])
                    {
                        case "UP":
                            e1.EventArguments[0].ArgumentName = "move";
                            e1.EventArguments[0].ArgumentValue = "up";
                            e1.EventArguments[0].ArgumentType = "string";
                            break;

                        case "DOWN":
                            e1.EventArguments[0].ArgumentName = "move";
                            e1.EventArguments[0].ArgumentValue = "down";
                            e1.EventArguments[0].ArgumentType = "string";
                            break;

                        case "RIGHT":
                            e1.EventArguments[0].ArgumentName = "move";
                            e1.EventArguments[0].ArgumentValue = "right";
                            e1.EventArguments[0].ArgumentType = "string";
                            break;

                        case "LEFT":
                            e1.EventArguments[0].ArgumentName = "move";
                            e1.EventArguments[0].ArgumentValue = "left";
                            e1.EventArguments[0].ArgumentType = "string";
                            break;
                        default:
                            break;
                    }

                    if (e1.EventName != "")
                        eventCollection.Add(e1);
                }
                String smouseInformation = (String)input2.getInput();
                smouseInformation = smouseInformation.Trim().ToLower();
                inputs = smouseInformation.Split(',');

                Event e = new Event();
                e.EventName = "uimouse";

                e.EventArguments[0].ArgumentName  = "yvalue";
                e.EventArguments[0].ArgumentValue = inputs[0];
                e.EventArguments[0].ArgumentType = "int";

                if (inputs[1] == "left")      // flick paddle's left side
                {
                    e.EventArguments[0].ArgumentName  = "mbpressed";
                    e.EventArguments[0].ArgumentValue = "left";
                    e.EventArguments[0].ArgumentType = "string";
                }
                else if (inputs[1] == "right" )      // flick paddle's right side
                {
                    e.EventArguments[0].ArgumentName  = "mbpressed";
                    e.EventArguments[0].ArgumentValue = "right";
                    e.EventArguments[0].ArgumentType = "string";
                }

                if (e.EventName != "")
                    eventCollection.Add(e);
            }

        }

        /// <summary>
        /// Sets the value of 'MenuDisplayed' to false indicating that the main menu needs to be displayed.
        /// Creates an input stream for getting the menu input from the keyboard
        /// </summary>
        public override void Initialize()
        {
            // Creates input stream for menu input
            IGUIFactory guiFactory = createInputStream(INPUTSOURCE.KEYBOARD);        
            input0 = guiFactory.createInputStream();
            MenuDisplayed = false;
            base.Initialize();
        }

        /// <summary>
        /// Displays menu OR Gets the input from user for moving paddles
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            if (MenuDisplayed == false)     // Menu input still needs to be taken
            {
                getMenuInput();
            }
            else                            // Get the user input for the paddles
            {
                // Use accumulator to make sure that user input is accepted at a certain rate, i.e. 'Resources.MIN_INPUT_TIME'
                if (fInputTimeAccumulator < Resources.MIN_INPUT_TIME)
                {
                    fInputTimeAccumulator += (float)gameTime.ElapsedGameTime.TotalSeconds;
                }
                else
                {
                    getInput();
                    fInputTimeAccumulator = 0;
                }
                
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// Gets input from the menu
        /// </summary>
        private void getMenuInput()
        {
            String menuInputKey = (String)input0.getKeyboardInput();
            IGUIFactory guiFactory;

            switch (menuInputKey)
            {
                case "D1":   // Single player keyboard
                    guiFactory = createInputStream(INPUTSOURCE.KEYBOARD);
                    input1 = guiFactory.createInputStream();
                    GameDescription = Resources.GAMEDESCRIPTION.ONE_PLAYER_KEYBOARD;
                    MenuDisplayed = true;
                    break;
                case "D2":   // Single player mouse
                    guiFactory = createInputStream(INPUTSOURCE.MOUSE);
                    input1 = guiFactory.createInputStream();
                    GameDescription = Resources.GAMEDESCRIPTION.ONE_PLAYER_MOUSE;
                    game.IsMouseVisible = true;
                    MenuDisplayed = true;
                    break;
                case "D3":   // Two players keyboard
                    guiFactory = createInputStream(INPUTSOURCE.KEYBOARD);
                    input1 = guiFactory.createInputStream();
                    GameDescription = Resources.GAMEDESCRIPTION.TWO_PLAYERS_KEYBOARD;
                    MenuDisplayed = true;
                    break;
                case "D4":   // Simulation
                    GameDescription = Resources.GAMEDESCRIPTION.SIMULATION;
                    MenuDisplayed = true;
                    break;
                case "D5":   // 
                    GameDescription = Resources.GAMEDESCRIPTION.TWO_PLAYERS_KEYBOARD_MOUSE;
                    guiFactory = createInputStream(INPUTSOURCE.KEYBOARD);
                    input1 = guiFactory.createInputStream();
                    guiFactory = createInputStream(INPUTSOURCE.MOUSE);
                    input2 = guiFactory.createInputStream();
                    game.IsMouseVisible = true;
                    MenuDisplayed = true;
                    break;
                case "esc":
                    game.Exit();
                    break;
                default:
                    MenuDisplayed = false;
                    break;
            }
        
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// It will display the menu if 'Resources.isMenuDisplayed' is false
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Draw(GameTime gameTime)
        {
            if (MenuDisplayed == false)   // Display the menu
            {
                displayText(menu, "center");
            }
            else
            {
                displayText("Press escape to return to main menu.", "bottom");
            }
            base.Draw(gameTime);
        }
    }



    /// <summary>
    /// Event arguments for OnMenuWasDisplayed event
    /// </summary>
    public class MenuDisplayedEventArgs : EventArgs
    {
        public readonly bool bMenuDisplayed;

        public MenuDisplayedEventArgs(bool menuDisplayed)
        {
            bMenuDisplayed = menuDisplayed;
        }
    }

    /// <summary>
    /// Event arguments for OnMenuWasDisplayed event
    /// </summary>
    public class GameDescriptionSetEventArgs : EventArgs
    {
        public readonly Resources.GAMEDESCRIPTION egameDescription;

        public GameDescriptionSetEventArgs(Resources.GAMEDESCRIPTION gameDescription)
        {
            egameDescription = gameDescription;
        }
    }

    
    /// <summary>
    /// 
    /// </summary>
    public interface IInput
    {
        Object getInput();
        Object getKeyboardInput();
    }

    public interface IGUIFactory
    {
        IInput createInputStream();
    }


    /// <summary>
    /// Implements the getInput() function to retrieve keyboard input for controlling paddles
    /// Implements the getKeyboardInput() function to retrieve keyboard input for the menu
    /// </summary>
    public class KeyboardInput : IInput
    {
        // Used only for input from keyboard
        KeyboardState lastKeyboardState;
        KeyboardState currentKeyboardState;

        /// <summary>
        /// Returns input from the keyboard for the menu
        /// </summary>
        public Object getKeyboardInput()
        {
            currentKeyboardState = Keyboard.GetState();
            String pressedKey = "";
            if (currentKeyboardState.IsKeyDown(Keys.D1) && lastKeyboardState.IsKeyUp(Keys.D1))      // Option 1 selected
            {
                pressedKey = "D1";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.D2) && lastKeyboardState.IsKeyUp(Keys.D2))   // Option 2 selected
            {
                pressedKey = "D2";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.D3) && lastKeyboardState.IsKeyUp(Keys.D3))   // Option 3 selected
            {
                pressedKey = "D3";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.D4) && lastKeyboardState.IsKeyUp(Keys.D4))   // Option 4 selected
            {
                pressedKey = "D4";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.D5) && lastKeyboardState.IsKeyUp(Keys.D5))   // Option 5 selected
            {
                pressedKey = "D5";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.Escape) && lastKeyboardState.IsKeyUp(Keys.Escape))   // Escape selected
            {
                pressedKey = "esc";
            }
            lastKeyboardState = currentKeyboardState;
            return pressedKey;
        }

        /// <summary>
        /// Implements the getInput() function to retrieve keyboard input for users
        /// </summary>
        public Object getInput()
        {
            currentKeyboardState = Keyboard.GetState();

            String pressedKey1 = "";
            String pressedKey2 = "";

            // Check for input from first player
            if (currentKeyboardState.IsKeyDown(Keys.Up) )   // UP arrow key has been pressed this frame
            {
                pressedKey1 = "UP";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.Down) )   // DOWN arrow key has been pressed this frame
            {
                pressedKey1 = "DOWN";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.Right))   // RIGHT arrow key has been pressed this frame
            {
                pressedKey1 = "RIGHT";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.Left) )   // LEFT arrow key has been pressed this frame
            {
                pressedKey1 = "LEFT";
            }


            // Check for input from second player
            if (currentKeyboardState.IsKeyDown(Keys.W) )   // UP arrow key for player2 has been pressed this frame
            {
                pressedKey2 = "W";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.S) )   // DOWN arrow key for player2 has been pressed this frame
            {
                pressedKey2 = "S";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.D) )   // RIGHT arrow key for player2 has been pressed this frame
            {
                pressedKey2 = "D";
            }
            else if (currentKeyboardState.IsKeyDown(Keys.A) )   // LEFT arrow key for player2 has been pressed this frame
            {
                pressedKey2 = "A";
            }

            lastKeyboardState = currentKeyboardState;
            return (String)pressedKey1 + " " + (String)pressedKey2;
        }
    }
    
    /// <summary>
    /// Implements the getInput() function to retrieve mouse input
    /// </summary>
    public class MouseInput : IInput
    {
        // Used only for input from mouse
        MouseState lastMouseState;
        MouseState currentMouseState;

        public Object getKeyboardInput()    // Not required by MouseInput 
        {
            return null;
        }

        /// <summary>
        /// Returns a string containing the mousePosition, and the mouse button that is pressed
        /// </summary>
        public Object getInput()
        {
            String smouseInformation = "";
            currentMouseState = Mouse.GetState();

            smouseInformation = currentMouseState.Y.ToString();

            
            if (currentMouseState.RightButton == ButtonState.Pressed && lastMouseState.RightButton == ButtonState.Released)   // mouse's right button pressed this frame
            {
                smouseInformation += ",right";
            }
            else if (currentMouseState.LeftButton == ButtonState.Pressed && lastMouseState.LeftButton == ButtonState.Released)      // mouse's left button pressed this frame
            {
                smouseInformation += ",left";
            }
            else
            {
                smouseInformation += ",none";
            }

            lastMouseState = currentMouseState;

            return smouseInformation;
        }
    }

    /// <summary>
    /// Creates the KeyboardInput object for getting keyboard input
    /// </summary>
    public class KeyboardInputFactory : IGUIFactory
    {
        IInput IGUIFactory.createInputStream()
        {
            return new KeyboardInput();
        }
    }

    /// <summary>
    /// Creates the MouseInput object for getting mouse input
    /// </summary>
    public class MouseInputFactory : IGUIFactory
    {
        IInput IGUIFactory.createInputStream()
        {
            return new MouseInput();
        }
    }

}
